Добро пожаловать, Гость
Логин: Пароль: Запомнить меня
  • Страница:
  • 1
  • 2

ТЕМА: как получить синус с помощью ШИМ

как получить синус с помощью ШИМ 6 года 6 мес. назад #663

  • maxim
  • maxim аватар
  • Вне сайта
  • Fresh Boarder
  • Сообщений: 2
Помогите кто может!!! Каким образом получить синусоидальный сигнал, частотой 50Гц на микроконтроллере AVR с помощью ШИМ. На сайте уже видел генератор синуса но на выходе 120Гц, а мне нужно 50Гц. Не могу разобраться каким образом изменить частоту? программным путем или схемным?
Администратор запретил публиковать записи.

Re:как получить синус с помощью ШИМ 6 года 6 мес. назад #666

  • Pashgan
  • Pashgan аватар
  • Вне сайта
  • Administrator
  • Сообщений: 1019
  • Спасибо получено 58
Конечно программно. Частота аналогового сигнала генерируемого с помощью ШИМ-сигнала можно рассчитать по формуле.
Fs = Fckl/(256 * N * M)
где Fclk - тактовая частота микроконтроллера, N - коэффициент деления предделителя, M - число отсчетов сигнала.

На сайте есть пример генерации синуса частотой 122 Гц.
Откуда взялась эта частота?

Fs = 8000000/(256 * 8 * 32) = 122

тактовая частота 8 МГц, предделитель 8, число отсчетов синуса 32
Понятно?

Чтобы сгенерить синус частотой 50 Гц, нужно взять M = Fclk/(256* N * Fs) отсчетов. Для примера с сайта M будет равно 78.
Администратор запретил публиковать записи.

Re:как получить синус с помощью ШИМ 6 года 6 мес. назад #669

  • maxim
  • maxim аватар
  • Вне сайта
  • Fresh Boarder
  • Сообщений: 2
понял! большое спасибо!!!
Администратор запретил публиковать записи.

Re:как получить синус с помощью ШИМ 6 года 5 мес. назад #719

  • САБ
  • САБ аватар
  • Вне сайта
  • Gold Boarder
  • Сообщений: 200
  • Спасибо получено 13
А можно получить синус практически любой частоты (до Fpwm/2) используя принцип DDS. Если интересно - могу накидать исходник.
Администратор запретил публиковать записи.

Re:как получить синус с помощью ШИМ 6 года 5 мес. назад #720

  • solvler
  • solvler аватар
  • Вне сайта
  • Expert Boarder
  • Сообщений: 152
Интересно, накидай :)
Администратор запретил публиковать записи.

Re:как получить синус с помощью ШИМ 6 года 5 мес. назад #727

  • САБ
  • САБ аватар
  • Вне сайта
  • Gold Boarder
  • Сообщений: 200
  • Спасибо получено 13
Возьмем для примера мегу88, синус будем генерить на выходе OC0A.

Итак, делаем массив значений ШИМ на 1 период синуса. Индекс массива соответствует фазе синуса. Если массив будет состоять из 256 значений, то значение индекса 0 соответствует фазе 0, значение индекса 64 соответствует фазе Пи/2, 128 - Пи, 256 - 2*Пи. Если мы будем использовать байтовую переменную в качестве индекса массива, нам не надо будет следить за ее выходом за пределы массива, при фазе 2*Пи она станет равной 0.
Значения ШИМ у нас могут меняться от 0 до 0xFF, поэтому и значения в массиве должны быть в этом диапазоне:
#include    <stdint.h>
#include    <avr/pgmspace.h>

uint8_t PROGMEM sin_table[]
{
    0x7F, 0x7C, 0x79, 0x76, 0x73, 0x6F, 0x6C, 0x69, 0x66, 0x63, 0x60, 0x5D, 0x5A, 0x57, 0x54, 0x51, 
    0x4E, 0x4C, 0x49, 0x46, 0x43, 0x40, 0x3E, 0x3B, 0x38, 0x36, 0x33, 0x31, 0x2E, 0x2C, 0x2A, 0x27, 
    0x25, 0x23, 0x21, 0x1F, 0x1D, 0x1B, 0x19, 0x17, 0x15, 0x14, 0x12, 0x10, 0x0F, 0x0E, 0x0C, 0x0B, 
    0x0A, 0x09, 0x07, 0x06, 0x05, 0x05, 0x04, 0x03, 0x02, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 
    0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x02, 0x02, 0x03, 0x04, 0x05, 0x05, 0x06, 0x07, 0x09, 
    0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0x10, 0x12, 0x14, 0x15, 0x17, 0x19, 0x1B, 0x1D, 0x1F, 0x21, 0x23, 
    0x25, 0x27, 0x2A, 0x2C, 0x2E, 0x31, 0x33, 0x36, 0x38, 0x3B, 0x3E, 0x40, 0x43, 0x46, 0x49, 0x4C, 
    0x4E, 0x51, 0x54, 0x57, 0x5A, 0x5D, 0x60, 0x63, 0x66, 0x69, 0x6C, 0x6F, 0x73, 0x76, 0x79, 0x7C, 
    0x7F, 0x82, 0x85, 0x88, 0x8B, 0x8F, 0x92, 0x95, 0x98, 0x9B, 0x9E, 0xA1, 0xA4, 0xA7, 0xAA, 0xAD, 
    0xB0, 0xB2, 0xB5, 0xB8, 0xBB, 0xBE, 0xC0, 0xC3, 0xC6, 0xC8, 0xCB, 0xCD, 0xD0, 0xD2, 0xD4, 0xD7, 
    0xD9, 0xDB, 0xDD, 0xDF, 0xE1, 0xE3, 0xE5, 0xE7, 0xE9, 0xEA, 0xEC, 0xEE, 0xEF, 0xF0, 0xF2, 0xF3, 
    0xF4, 0xF5, 0xF7, 0xF8, 0xF9, 0xF9, 0xFA, 0xFB, 0xFC, 0xFC, 0xFD, 0xFD, 0xFD, 0xFE, 0xFE, 0xFE, 
    0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xFD, 0xFD, 0xFC, 0xFC, 0xFB, 0xFA, 0xF9, 0xF9, 0xF8, 0xF7, 0xF5, 
    0xF4, 0xF3, 0xF2, 0xF0, 0xEF, 0xEE, 0xEC, 0xEA, 0xE9, 0xE7, 0xE5, 0xE3, 0xE1, 0xDF, 0xDD, 0xDB, 
    0xD9, 0xD7, 0xD4, 0xD2, 0xD0, 0xCD, 0xCB, 0xC8, 0xC6, 0xC3, 0xC0, 0xBE, 0xBB, 0xB8, 0xB5, 0xB2, 
    0xB0, 0xAD, 0xAA, 0xA7, 0xA4, 0xA1, 0x9E, 0x9B, 0x98, 0x95, 0x92, 0x8F, 0x8B, 0x88, 0x85, 0x82, 
};
Будем использовать режим phase correct PWM, поскольку он дает наименьшие искажения (уровень гармоник) выходного синуса.
В этом режиме таймер считает от 0 до 0xFF, затем до 0 и снова до 0xFF ( 0x00, 0x01, ... 0xFE, 0xFF, 0xFE, ... 0x01, 0x00, 0x01 ). Можно заметить, что за каждый цикл таймер проходит дважды каждое значение кроме 0x00 и 0xFF (их по 1 разу). Поэтому полный цикл будет не 512 тиков, а 510.
В прерывании таймера мы будем расчитывать очередную фазу сигнала, расчитывать из этой фазы индекс массива, брать очередное значение из массива и класть его в регистр OCR0A.


Если мы будем выбирать из этого массива значения подряд (индекс будет увеличиваться на 1 в каждом прерывании), то получим выходную частоту, равную T0CLK/510/256. Если мы будем увеличивать индекс на 2, то весь массив будет пройден вдвое быстрее, т.е. получим частоту (T0CLK/510/256)*2, если будем увеличивать индекс на 3, то получим частоту (T0CLK/510/256)*3. Итого, шаг перестройки частоты будет (T0CLK/510/256), что для кварца 4МГц даст минимальную частоту и шаг перестройки примерно 30.6 Гц. Максимальная частота, согласно Котельникову, будет при выборке двух точек на период и составит (T0CLK/510/256) * 127 =~ 3891Гц. Чтобы ее увеличить, надо или увеличить частоту кварца или уменьшить период таймера (скажем, чтобы он считал не до 0xFF, а до 0x80) и соответствующим образом скорректировать значения в массиве. А что делать, если мы хотим получить частоту, скажем T0CLK/510/256 * 1.5? Можно было бы использовать арифметику с плавающей точкой, но она требует слишком больших вычислительных ресурсов, а у нас есть только 510 тактов между прерываниями таймера. Нам на выручку придет арифметика с фиксированной точкой. Мы возьмем двухбайтовую переменную в качестве счетчика (аккумулятора) фазы и будем считать старший байт целой частью, а младший - дробной частью. Подробнее о числах с фиксированной точкой можно почитать тут: compgraphics.info/2D/fixed.php. Целая часть аккумулятора фазы будет играть роль индекса массива. Приняв такой формат, мы можем изменять индекс с шагом 1/256 и, соответственно, получим минимальную частоту и шаг перестройки (T0CLK/510/256)/256 =~ 0.12Гц, а максимальную - (T0CLK/510/256/256)*(127*256 + 255)= ~3921Гц
#define FREQUENCY   1234.5    // Hz

#define T0CLK       F_CPU

//#define PHASE_STEP  (uint16_t)((FREQUENCY) / (T0CLK / 510 / 256) * 256)
// последнее * 256 - перевод в формат с фиксированной точкой 8.8
 
// Слегка перегруппируем формулу, чтобы избежать ошибок округления при вычислении в целых числах.
// Сделаем один из множителей типа unsigned long long, чтобы при целом FREQUENCY все выражение вычислялось в unsigned long long.
// В противном случае выражение будет вычисляться в int и возникнет переполнение.
#define PHASE_STEP  (uint16_t)(((FREQUENCY) * 256ULL * 256 * 510) / T0CLK)

#include    <avr/io.h>
#include    <avr/interrupt.h>
#include    <avr/wdt.h>
int main()
{
    // настраиваем таймер на phase correct PWM, c максимальным значением 0xFF, на максимальной частоте.
    // ШИМ на OCR0B будет формироваться в режиме Clear OC0A on Compare Match when up-counting,
    // Set OC0A on Compare Match when down-counting.
    TCCR0A = (1<<COM0A1)|(0<<COM0A0)|(0<<COM0B1)|(0<<COM0B0)|(1<<WGM01)|(1<<WGM00);
    TCCR0B = (0<<FOC0A)|(0<<FOC0B)|(0<<WGM02)|(0<<CS02)|(0<<CS01)|(1<<CS00);
    // Обновлять регистр ШИМ будем в прерывании по переполнению таймера
    TIMSK0 = (1<<TOIE0);
 
    // Настраиваем вывод OC0A (PD6) на выход.
    DDRD =  (0<<7)|(1<<6)|(0<<5)|(0<<4)|(0<<3)|(0<<2)|(0<<1)|(0<<0);
 
    sei();  // разрешаем прерывания
 
    for(;;)
    {
        // все происходит в прерывании
        wdt_reset();
    }
}
// добавим вспомогательную функцию получения значения ШИМ для конкретной фазы. Пока она простая, потом мы ее улучшим:
static inline uint8_t get_sinus(uint8_t phase)
{
    return pgm_read_byte(&sin_table[phase]);
}
 
// Объявляем переменную аккумулятора фазы, ее старший байт будет использоваться как индекс в таблице.
// Переменная используется _только_в_одном_ прерывании, поэтому объявляем ее без квалификатора volatile:
uint16_t Sin_phase;
 
// и, собственно, прерывание.
ISR(TIMER0_OVF_vect)
{
    // Увеличиваем фазу.
    Sin_phase += PHASE_STEP;
 
    // И используем старший байт как индекс массива.
    OCR0A = get_sinus(Sin_phase >> 8);
}
Завтра постараюсь написать, как можно почти в 4 раза уменьшить таблицу.

P.S. А что же этот форум так корежит исходники по ширине?
Последнее редактирование: 6 года 5 мес. назад от САБ.
Администратор запретил публиковать записи.
Спасибо сказали: Alex

Re:как получить синус с помощью ШИМ 10 мес. 2 нед. назад #4802

  • Alex
  • Alex аватар
  • Вне сайта
  • Fresh Boarder
  • Сообщений: 2
Добрый вечер! Ветка совсем завяла. А помощь очень нужна в понимании как реализовать арифметику с фиксированной точкой и именно при формировании синуса

САБ пишет:
А что делать, если мы хотим получить частоту, скажем T0CLK/510/256 * 1.5? Можно было бы использовать арифметику с плавающей точкой, но она требует слишком больших вычислительных ресурсов, а у нас есть только 510 тактов между прерываниями таймера. Нам на выручку придет арифметика с фиксированной точкой. Мы возьмем двухбайтовую переменную в качестве счетчика (аккумулятора) фазы и будем считать старший байт целой частью, а младший - дробной частью. Подробнее о числах с фиксированной точкой можно почитать тут: compgraphics.info/2D/fixed.php. Целая часть аккумулятора фазы будет играть роль индекса массива. Приняв такой формат, мы можем изменять индекс с шагом 1/256 и, соответственно, получим минимальную частоту и шаг перестройки (T0CLK/510/256)/256 =~ 0.12Гц, а максимальную - (T0CLK/510/256/256)*(127*256 + 255)= ~3921Гц
.
Мне, объяснения Pashganа и пример САБа очень помогли, понять и освоить принцип формирования синус и за вечер с нуля смог накидать простенькую прогу формирования синусоиды 50 Гц на миландровском контроллере. Нужно научиться формировать синус от 0 до 50 Гц с шагом, хотя бы 1 Гц.Камень плавающую запятую не поддерживает. Вот и хочу увидеть как применяется арифметика с фиксированной точкой, на живом примере формирования синуса. Прошу помочь.
Последнее редактирование: 10 мес. 2 нед. назад от Alex.
Администратор запретил публиковать записи.

Re:как получить синус с помощью ШИМ 10 мес. 2 нед. назад #4805

  • САБ
  • САБ аватар
  • Вне сайта
  • Gold Boarder
  • Сообщений: 200
  • Спасибо получено 13
Работа с фиксированной точкой объяснена по ссылке из сообщения выше вашего (#727).

В сообщении выше есть формула
#define PHASE_STEP  (uint16_t)(((FREQUENCY) * 256ULL * 256 * 510) / T0CLK)
Сделайте PHASE_STEP переменной и расчитывайте ее значение из нужной вам частоты по этой формуле. Не забудьте дать этой переменной квалификатор volatile, поскольку считаться она будет в основном цикле, а использоваться - в прерывании.

P.S. да, а об уменьшении таблицы я так и не написал тогда. Ну да там все просто - используется то свойство, что значения синуса для любого квадранта можно получить из таблицы для первого квадранта меняя знак аргумента и/или результата.
Последнее редактирование: 10 мес. 2 нед. назад от САБ.
Администратор запретил публиковать записи.
Спасибо сказали: Alex

Re:как получить синус с помощью ШИМ 10 мес. 2 нед. назад #4809

  • Alex
  • Alex аватар
  • Вне сайта
  • Fresh Boarder
  • Сообщений: 2
Вот затупил, честно. Никак не въеду как получаем значение регистра сравнения применительно к формированию шим синус. САБ, выручайте, если не трудно, накидайте пример формирования шим с фиксированной точкой, пожалуйста. Как уменьшить размер таблицы я понимаю и представляю. По volatile знаю, научен своим опытом. Спасибо, что ответили.
Администратор запретил публиковать записи.

Re:как получить синус с помощью ШИМ 10 мес. 2 нед. назад #4810

  • САБ
  • САБ аватар
  • Вне сайта
  • Gold Boarder
  • Сообщений: 200
  • Спасибо получено 13
Alex пишет:
Никак не въеду как получаем значение регистра сравнения
Из таблицы:
OCR0A = get_sinus(Sin_phase >> 8);
// добавим вспомогательную функцию получения значения ШИМ для конкретной фазы.
static inline uint8_t get_sinus(uint8_t phase)
{
    return pgm_read_byte(&sin_table[phase]);
}
Alex пишет:
САБ, выручайте, если не трудно, накидайте пример формирования шим с фиксированной точкой,
Так вон же он, в сообщении 727. Sin_phase - число с фиксированной точкой. Положение этой точки (между битами 7 и 8) мы держим в голове. При извлечении из таблицы используется только целая часть.
Последнее редактирование: 10 мес. 2 нед. назад от САБ.
Администратор запретил публиковать записи.
  • Страница:
  • 1
  • 2
Время создания страницы: 1.285 секунд